
export interface CodecType {

    size(): number,
    encode(buf: number[], index: number): void,
    decode(buf: number[], index: number): void,
}

export abstract class Num implements CodecType {

    value: number;

    constructor(v: number) {
        this.value = v;
    }

    abstract size(): number;
    abstract encode(buf: number[], index: number): void;
    abstract decode(buf: number[], index: number): void;

    isBitSet(bit: number) {
        return (this.value & (0x01 << bit)) != 0;
    }
}

export const encode = {

    u8(buf: number[], index: number, value: number) {
        buf[index] = value & 0xFF;
    },

    u16(buf: number[], index: number, value: number) {
        buf[index] = (value >> 8) & 0xFF;
        buf[index + 1] = value & 0xFF;
    },
    
    u32(buf: number[], index: number, value: number) {
        buf[index] = (value >> 24) & 0xFF;
        buf[index + 1] = (value >> 16) & 0xFF;
        buf[index + 2] = (value >> 8) & 0xFF;
        buf[index + 3] = value & 0xFF;
    }

}

export const decode = {

    u8(buf: number[], index: number) {
        return buf[index];
    },

    u16(buf: number[], index: number) {
        return (buf[index] << 8) + buf[1 + index];
    },

    u32(buf: number[], index: number) {
        return (buf[index] << 24) + 
        (buf[1 + index] << 16) + 
        (buf[2 + index] << 8) + 
        buf[3 + index];
    }
}

export class ByteView implements CodecType {
    value: number[];

    constructor(v: number[] = []) {
        this.value = v;
    }

    size() { 
        return 1 + this.value.length;
    }
    encode(buf: number[], index: number) {
        encode.u8(buf, index, this.value.length);
        for (let i = 0; i < this.value.length; i ++) {
            buf[i + index + 1] = this.value[i];
        }
    }
    decode(buf: number[], index: number) {
        const len = buf[index];
        this.value = buf.slice(1 + index, 1 + index + len);
    }

    toString() {
        const u8s = new Uint8Array(this.value);
        return new TextDecoder('utf-8').decode(u8s);
    }
}

export class U32 extends Num {

    constructor(v: number = 0) {
        super(v);
    }

    size() { 
        return 4;
    }
    encode(buf: number[], index: number) {
        encode.u32(buf, index, this.value);
    }
    decode(buf: number[], index: number) {
        this.value = decode.u32(buf, index);
    }
}

export class U16 extends Num {

    constructor(v: number = 0) {
        if (v < 0) {
            v = 0x10000 + v;
        }
        super(v);
    }

    signedValue() {
        if (0x00 != (this.value & 0x8000)) {
            return -(0x10000 - this.value);
        }
        return this.value;
    }

    size() { 
        return 2;
    }
    encode(buf: number[], index: number) {
        encode.u16(buf, index, this.value);
    }
    decode(buf: number[], index: number) {
        this.value = decode.u16(buf, index);
    }
}

export class U8 extends Num {

    constructor(v: number = 0) {
        super(v);
    }

    size() { 
        return 1;
    }
    encode(buf: number[], index: number) {
        encode.u8(buf, index, this.value);
    }
    decode(buf: number[], index: number) {
        this.value = decode.u8(buf, index);
    }
}

export function make(...ts: CodecType[]) {
    let size = 0;
    for (const v of ts) {
        size += v.size();
    }
    let index = 0;
    let buf = new Array<number>(size);
    for (const v of ts) {
        v.encode(buf, index);
        index += v.size();
    }
    return buf;
}

export function parse(buf: number[], ...ts: CodecType[]) {
    let index = 0;
    for (const v of ts) {
        v.decode(buf, index);
        index += v.size();
    }
}


